package com.ww.util.httpclient;

import com.alibaba.fastjson.JSON;
import org.apache.hc.client5.http.ClientProtocolException;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.util.Timeout;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;

public class GameHttpClient {

    // 线程池管理器
    /**
     * PoolingHttpClientConnectionManager是个复杂的类，它管理着连接池，可以同时为很多线程提供http连接请求。当请求一个新的连接时，
     * 如果连接池有可用的持久连接，连接管理器就会使用其中的一个，而不是再创建一个新的连接。
     *
     * PoolingHttpClientConnectionManager维护的连接数在每个路由基础和总数上都有限制。默认，每个路由基础上的连接不超过2个，总连接数不
     * 超过20。在实际应用中，这个限制可能会太小了，尤其是当服务器也使用Http协议时。
     */
    private static PoolingHttpClientConnectionManager poolConnManager = null;

    // 线程安全的，所有的线程都可以使用它一起发送http请求
    private static CloseableHttpClient httpClient;

    static {
        try {
            System.out.println("初始化");
            SSLContextBuilder builder = new SSLContextBuilder();
            builder.loadTrustMaterial(null, new TrustAllStrategy());
            SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(builder.build());
            // 配置同时支持HTTP、HTTPS
            Registry<ConnectionSocketFactory> factoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", factory).build();
            // 初始化连接管理器
            poolConnManager = new PoolingHttpClientConnectionManager(factoryRegistry);
            // 最大连接数
            poolConnManager.setMaxTotal(640);
            // 最大路由
            poolConnManager.setDefaultMaxPerRoute(320);
            /**
             * maxTotal和defaultMaxPerRoute区别
             * 1、maxTotal是整个池子的大小；
             * 2、defaultMaxPerRoute是根据连接到的主机对maxTotal的一个细分，比如maxTotal=400 defaultMaxPerRoute=200
             * 只直接到http://www.abc.com时，到这个主机的并发最多只有200，而不是400
             * 连接到http://www.bac.com和http://www.ccd.com时，到每个主机的并发最多只有200，即加起来400
             */

            httpClient = getConnection();
            System.out.println("初始化结束");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (KeyStoreException e) {
            throw new RuntimeException(e);
        } catch (KeyManagementException e) {
            throw new RuntimeException(e);
        }
    }

    public static CloseableHttpClient getConnection() {
        // 定义RequestConfig对象来设置请求时的时间参数，如连接创建时间、从连接池中获取连接时间、数据传输请求时间、是否请求前测试可用性等
        // RequestConfig对象可以为整个httpClient设置，也可以针对GET或POST请求分别设置
        RequestConfig config = RequestConfig.custom().setConnectTimeout(Timeout.of(50, TimeUnit.SECONDS)).setConnectionRequestTimeout(Timeout.of(50, TimeUnit.SECONDS)).build();
        HttpClients.custom()
                // 设置连接池管理
                .setConnectionManager(poolConnManager)
                .setDefaultRequestConfig(config).build();
                // 设置重试次数
//                .setRetryHandler(new DefaultHttpRequestRetryHandler(2, false)).build();
        return httpClient;
    }

    public static String httpGet(String url) {
        HttpGet httpGet = new HttpGet(url);
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpGet);
            String result = EntityUtils.toString(response.getEntity());
            int code = response.getCode();
            if (HttpStatus.SC_OK == code) {
                return result;
            } else {
                System.out.println("请求" + url + "返回错误" + result);
                return null;
            }
        } catch (ClientProtocolException | ParseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != response) {
                try {
                    // 执行httpResponse.close关闭对象会关闭连接池
                    // 如果需要将连接释放到连接池，可以使用EntityUtils.consume()方法
                    // response.close();
                    EntityUtils.consume(response.getEntity());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static String post(String uri, Object params, Header... headers) {
        HttpPost httpPost = new HttpPost(uri);
        CloseableHttpResponse response = null;
        try {
            StringEntity entity = new StringEntity(JSON.toJSONString(params));
//            entity.setContentEncoding("UTF-8");
//            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            if (null != headers) {
                httpPost.setHeaders(headers);
            }
            response = httpClient.execute(httpPost);
            int code = response.getCode();
            String result = EntityUtils.toString(response.getEntity());
            if (HttpStatus.SC_OK == code) {
                return result;
            } else {
                System.out.println("请求" + uri + "返回错误码" + code + ", 响应参数" + result);
                return null;
            }
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (ClientProtocolException e) {
            throw new RuntimeException(e);
        } catch (IOException | ParseException e) {
            throw new RuntimeException(e);
        } finally {
            if (null != response) {
                try {
                    response.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
